package org.fjsei.yewu.bpm;


import io.camunda.zeebe.client.api.response.ProcessInstanceEvent;
//import io.camunda.zeebe.spring.client.lifecycle.ZeebeClientLifecycle;
import io.camunda.zeebe.client.ZeebeClient;
import md.cm.flow.ApprovalStm;
import md.cm.flow.ApprovalStmRepository;
import md.log.Circulation;
import md.log.CirculationRepository;
import md.log.Opinion_Enum;
import md.specialEqp.inspect.IspRepository;
import md.specialEqp.inspect.Procedure_Enum;
import md.system.User;
import org.fjsei.yewu.filter.FlowStat;
import org.fjsei.yewu.resolver.Comngrql;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 业务逻辑方法  和 实体类存储类 分离开。 ZeebeClient依赖于开关 zeebe.client.enabled: false 可能不存在改Bean
*/
@ConditionalOnBean(ZeebeClient.class)
@Service
public class ApprovalStmServiceImpl extends Comngrql implements ApprovalStmService {

    @Autowired
    private CirculationRepository circulationRepository;
    @Autowired
    private ApprovalStmRepository approvalStmRepository;
    @Autowired
    private IspRepository iSPRepository;

    //旧版本的文档来自 https://github.com/camunda-community-hub/spring-zeebe
    private final ZeebeClient zeebeClient;
    //旧的是 private final ZeebeClientLifecycle zeebeClient;
    @PersistenceContext(unitName = "entityManagerFactorySei")
    private EntityManager emSei;

    public ApprovalStmServiceImpl(ZeebeClient zeebeClient) {
        this.zeebeClient = zeebeClient;
    }

    /**发起一个新的审批流程的实例，初始化流程变量。
     *@param bpmn , 当前BPMN: .bpmnProcessId("ReportApproval");
     * 校核人启动报告的流转。
     * 流转动作: 这里仅仅登记历史操作就行了，实际由zeebe管控之后的流程运行图。
     * stm:进来之前就得知当前流程节点了。
     * 返回的流程实例ID=reference，但无法从该id获得UserTask的相关信息，用户任务是流程引擎发出的，没做id关联，目前只能通过流程变量来关联，UserTask仅是流程图单一节点操作。
       每个UserTask都要配置的自定义头部：用户任务目标授权用户assignee 或授权用户组s candidateGroups;
     * 若BPMN文件定义如此的：candidateGroups="=[bossg,tomens]" 必须映射这两个用户组。
     * 【规定】用户组名字不要含有","逗号[和]的三个；可以直接给BPMN定义文件设置单一个变量名:assignGroups,拼接授权用户组字符串[bossg,tomens]。
     * boss: 审核人员。
     * zeebe直接挂钩关系数据库的User.username字段；所以同一个流转引擎服务集群实际挂接单一个关系数据库的用户实体表，UserTask依靠.username来定位唯一性。
     * */
    public String startFlow(String bpmn,String topic,String entId, String uri,ApprovalStm stm, Opinion_Enum opinion,String  memo,
                                Integer days, List<User> authr, Boolean subitem){
        User user=checkAuth();
        //有延迟： 这里发起流程之后：大约10秒钟才能在TaskList网页上面看见到的。 todo: boss=审核人 Report里面预设值
        //String userId= user.getUsername();    //Tool.toGlobalId("User",user.getId());
        UniversalProcessVariables variables=new UniversalProcessVariables().setAuthor(user.getUsername()).setUri(uri)
                .setEnt(entId).setReviewer(stm.getReviewer().getUsername()).setApprover(stm.getApprover().getUsername())
                .setTopic(topic);
        //variables.setBossg(boss);
        //variables.setAssignGroups(List.of("审核小组","group3"));     //特殊数组变量  2019-10-02T08:09:40+08:00
        //variables.setExpirationDate("2022-06-27T11:05:00+08:00");
        //variables.setCreationDate("2022-06-27T11:03:00+08:00");  前后都用"2022-06-27T11:03:00"可以
        Instant instant=Instant.now();
        //   variables.setCreationDate(instant.toString());
        //List<String> uids= signMens.stream().map(men->Tool.toGlobalId("User",men.getId())).collect(Collectors.toList());
        List<String> signMens= authr.stream().map(User::getUsername).collect(Collectors.toList());
        variables.setIspMens(signMens);     //签字检验员
        variables.setSubitem(subitem);     //主报告的
        //variables.setUri(uri);      "/inspect/fCfLZtwOQ_6HPHewrbtGZUlzcA/report/fc25TU-9Tb6RopbXtb4Ws1JlcG9ydA"
        //只能用字符串表达时间变量；     3分钟之后
        variables.setExpirationDate(instant.plus(days, ChronoUnit.DAYS).toString());

        ProcessInstanceEvent processInstance= zeebeClient.newCreateInstanceCommand()
                .bpmnProcessId(bpmn)
                .latestVersion()
                .variables(variables)
                .send().join();     //blocking call!

        //登记状态机日志加一条 + 状态机状态修正同步。
        Circulation circ=new Circulation();
        circ.setApply(stm);     //附属于流程对象
        circ.setCurrent(stm.getSta());
        //circ.setNext(next);
        circ.setMemo(memo);
        circ.setUser(user);
        circ.setOpinion(opinion);
        //Set<User> backAuthUsers=new HashSet<>();
        Set<User>  otherUsers= stm.getAuthr().stream().filter(one->!one.equals(user)).collect(Collectors.toSet());
        //stm.getAuthr().remove(user);
        circ.setMoreUser(new ArrayList<>(otherUsers));     //出错了? moreUser[]里面不允许包含User user ？报错 还是因为stm.getAuthr().remove(user)影响的
        circulationRepository.save(circ);
        stm.setCur(circ);
        stm.getHis().add(circ);
        //stm.setSta(next);     流程下一个flowNode实际在zeebe流程引擎上决定的，这里不需要知晓。
        stm.setAuthr(authr);
        //流程引擎给的流程实例id =reference 必须保存DB数据库。
        stm.setPrId(processInstance.getProcessInstanceKey());
        stm.setSta(Procedure_Enum.SIGN);      //可以提前预判必然进入会签了；
        approvalStmRepository.save(stm);

        return  String.valueOf( processInstance.getProcessInstanceKey() );
    }


//    public Iterable<Isp> findAllISPfilter(Long taskId) {
//        emSei.contains(new Task());
//         List<Isp> allPage2 = iSPRepository.findAll();
//        return allPage2;
//    }


    /**在流程实例已经生成后, 各个环节的用户任务的处理。
     * 流程UserTask节点对应的用户流转命令处理：
     * 参数FlowStat ent：每个申请单或报告都会关联一个DB状态记录流转历史。 ent.getStm()代表状态机；
     * 若Set<User> authr没有提供不修改，直接从ent.getStm().authr来缺省配上, 流转给谁。ApprovalStm提供初始化的配置
     * ent参数: 是关联的报告或申请单实体DB模型。
     * uri参数: 可修改给下一个UserTask(后退？)的前端页面链接。
     * days参数：下一个节点超时时间天数。允许修改zeebe流程变量 规范化 超时时间变量。最少1天整数的，普通流程不可能十分紧急严格到几个小时分钟，用户还未能实时通知到。
     * */
    @Override
    public String flowTo(String userTaskId, FlowStat ent, Opinion_Enum opinion, String memo, Integer days, Set<User> mens, String uri) {
        User user=checkAuth();
        //完成本环节用户任务  variables 没有修改部分不用get 后set;
        //Map<String, Object> 变量：Collections.singletonMap("approved", true) 多个变量，时间期限，同意，回退理由,转给多人多用户组。
        //每个流程flowNode所考察的 variables 不见得名字一致的？ 统一化的映射zeebe的模型变量名。
        ApprovalStm stm= ent.getStm();	  //仅仅是状态和历史操作记录，不是真正的流程引擎服务。
        if(null==stm)   return "没状态机";
        Map<String, Object>  variables= new HashMap<>();
        //Map<String, Object>  variables= Collections.singletonMap("approved", true);
        //variables.put("result", "true"); 报错啊failed to evaluate expression ' result=false': expected String but found 'ValBoolean(false)'

        //签字阶段实际用这个变量，而不是approved变量的。 .put("result", "true")报错！ NO BACK
        boolean lzResult= opinion != Opinion_Enum.NO && opinion != Opinion_Enum.BACK;
        //【关键触发】控制流程走向。
        variables.put("result", lzResult);     //不能用字符串，流程引擎zeebe会出现解析错误
        if(StringUtils.hasText(uri))      variables.put("uri", uri);
        if(days>0) {
            Instant instant = Instant.now();
            variables.put("expirationDate", instant.plus(days, ChronoUnit.DAYS).toString());
                //UniversalProcessVariables.setExpirationDate();
        }
        //variables.put("approved", Boolean.TRUE); BPMN图上增加approved=result输出，能免去额外输入approved流程变量。直接用result默认变量替代。
   //     UniversalProcessVariables variables2=new UniversalProcessVariables().setUri(uri);
        //前后这两个变量变更办法的作用域不一样的。一个是上级主流程的，一个是本节点的变量传递需要赋值机制。
        //可实现但有缺点：需要获得流程实例ID,还要另外发送newSetVariablesCommand。想办法直接在BPMN图上定义变量的输出赋值模式。
        //zeebeClient.newSetVariablesCommand(流程实例ID xL).variables(variables).send().join();

        //改用.variables(UniversalProcessVariables x)会直接清空替换所有的变量！第一次才能用。 .variables(variables)
        zeebeClient.newCompleteCommand(Long.parseLong(userTaskId)).variables(variables).send().join();

        //审核或审批 登记状态机日志加一条 + 状态机状态修正同步。
        Circulation circ=new Circulation();
        circ.setApply(stm);     //附属于流程对象
        circ.setCurrent(stm.getSta());
        //circ.setNext(next);
        circ.setMemo(memo);
        circ.setUser(user);
        circ.setOpinion(opinion);
        circulationRepository.save(circ);
        stm.setCur(circ);
        stm.getHis().add(circ);
        //stm.setSta(Procedure_Enum.SIGN);  //无法在这设置流程状态？不明确含义：ApprovalStm不适合在这里变更！
        approvalStmRepository.save(stm);
        return userTaskId;
    }

}


